Skip to content

feat(objectql): publish DriverConnect, DatasourceUnavailable and SummaryRecompute error codes as constants - #16308

Draft
claude[bot] wants to merge 4 commits into
mainfrom
claude/issue-16159-driver-connect-summary-error-code-constants
Draft

feat(objectql): publish DriverConnect, DatasourceUnavailable and SummaryRecompute error codes as constants#16308
claude[bot] wants to merge 4 commits into
mainfrom
claude/issue-16159-driver-connect-summary-error-code-constants

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Part of #16159

Batch i of that card's sweep, per the batching ruling recorded on it: three rows of its table, in two files. Each of DriverConnectError, DatasourceUnavailableError and SummaryRecomputeError now publishes its ADR-0112 code as an importable constant, matching the shape set by #16283 and #16259.

⛔ Deliberately NOT in this PR, and untouched: packages/objectql/src/transaction-errors.ts (2 rows — batch ii, serialised behind this PR because both edit the barrel and the repo runs a single-writer-path check), packages/objectql/src/validation/record-validator.ts (1 row — held until VALIDATION_FAILED vs VALIDATION_ERROR is ruled), and content/docs/kernel/contracts/data-engine.mdx (#16281 tracks the doc sweep; publishing a constant makes that page's sentence true, not false).

Why these three rows

Each of the three classes already tells its reader, in its own docblock, that it is "Identified by code rather than instanceof so it survives crossing package boundaries" — and offered nothing to import. The only way to FOLLOW that published instruction was to re-spell the wire string in the consumer's own package, which acquires a check:error-code-provenance stamp site there and is then free to drift from what the engine throws with no compile error to say so.

That cost is already shipped, not hypothetical. Three first-party packages in this repo match these refusals by code today, and therefore carry a second spelling of the string:

consumer code it re-spells what it does with it
packages/rest/src/error-response.ts:1024 ERR_DATASOURCE_UNAVAILABLE matches, then re-authors the same spelling into the envelope it builds
packages/rest/src/import-runner.ts:537 ERR_SUMMARY_RECOMPUTE the documented "the records WERE written, treat it as a warning" recovery
packages/metadata-protocol/src/seed-loader.ts:2081 ERR_SUMMARY_RECOMPUTE the same recovery, independently spelled

⛔ This PR does not rewire those three call sites. That is a consumer-side change in two other packages, outside a producer-side sweep, and no gate asks for it. It is recorded here as the evidence for the card, and is filed separately as a follow-up.

Clause ② — graded per row, from a measurement

Batching changed how many PRs the sweep costs, ⛔ not how each row is graded. The instrument is the one this repo's rounds converged on: build at head → swap the changed sources back to their base → rebuild → compare exported NAME SETS across every declaration file files[] publishes (⛔ not bytes: tsup renumbers internal aliases and the shared chunk's filename is content-hashed, so a byte delta is dominated by noise that is not a surface change).

declaration file names BASE → HEAD reading
dist/index.d.ts 252 → 255 added exactly the three constants, removed none
dist/index.d.mts 252 → 255 identical delta on the other realm
dist/core.d.ts / .d.mts 79 → 79 the lean entry publishes nothing new
shared chunk util-*.d.ts / .d.mts 133 → 135 two more internal aliases (bf, bg) ⇒ correctly NOT surface
row new export Clause ② bump
driver-connect-errors.tsERR_DRIVER_CONNECT DRIVER_CONNECT_CODE yes — additive widening, nothing removed minor
driver-connect-errors.tsERR_DATASOURCE_UNAVAILABLE DATASOURCE_UNAVAILABLE_CODE yes — additive widening, nothing removed minor
summary-errors.tsERR_SUMMARY_RECOMPUTE SUMMARY_RECOMPUTE_CODE yes — additive widening, nothing removed minor

The three rows are separable in that measurement rather than lumped: each constant appears by name in the ADDED set, and REMOVED is empty in both realms.

⭐ Two of the three constants reach index.d.ts only through the shared chunk (aB as DATASOURCE_UNAVAILABLE_CODE, aE as DRIVER_CONNECT_CODE), while SUMMARY_RECOMPUTE_CODE is declared in index.d.ts directly. Counts, with a negative control at 0 on both sides: chunk 0 → 5 and 0 → 2 for the first two, 0 → 0 for the third; index.d.ts 0 → 1, 0 → 1, 0 → 3. The barrel's re-export line names all three either way, so the name-set reading holds — but the census was taken over every published declaration, not the root barrel alone.

Ablation hygiene, since these numbers depend on it: implementation committed first; mutation proven on disk before measuring (each path's git hash-object equal to the BASE blob, plus whole-line anchored greps at 0/0/0 for the new constants and 1/1/1 for the literals they replace); objectql rebuilt on each leg with tsup invoked directly, no turbo cache on the path, proven by dist/index.d.ts mtime moving 1788698128 → 1788698599 → 1788698619; restore proven byte-exact (git hash-object equal to the HEAD blob for all three paths, git diff HEAD empty, git status --porcelain empty ⇒ zero blast radius); and the restored build's six declaration files are byte-identical hashes to the head snapshot, so the base leg was a measurement rather than a blind spot.

The two inverted gates, measured rather than predicted

All three codes are registered in ERROR_CODE_LEDGER under @objectstack/objectql — like #16283's row and unlike #16259's. The prediction that follows was checked, not assumed:

  • check:error-code-provenance is NOT neutral. 311 → 314 registered-code stamp sites, 295 → 298 listed, waivers unchanged at 16, exit 0 on both sides. Exactly +1 per row: each new constdef is a stamp site this gate sees, and it passes because the code is listed under this package's own owner key.
  • check:dispatcher-error-vocabulary does not move. Verdict and scope lines are byte-identical either side (66 unregistered code-stamping sites, all classified; 2371 files; 303 registered codes), exit 0 both. That table records UNREGISTERED sites, so a registered code is invisible to it by construction — and no row in packages/runtime/src/dispatcher-error-vocabulary.ts changes. No cross-package edit is owed for any of these three rows.

Shape

Follows #16283 exactly: export const X_CODE = '…' as const; immediately above its class, the class field as a bare readonly code = X_CODE; (⛔ no as const on the FIELD — that spelling is what check:dispatcher-error-vocabulary classifies as classconst), re-exported from index.ts beside the class. Each string is byte-identical to the literal it replaces: the quoted wire spelling occurs exactly once per code in its file on both sides of the change — it moved, it did not multiply or mutate.

⚠️ One asymmetry this PR records and deliberately does not decide. DriverConnectError and DatasourceUnavailableError are published from the lean ./core entry as well as from the batteries barrel, while these constants — like every other *_CODE in this package — go on the batteries barrel only. That is #16260's subject for the whole family; deciding it inside a mechanical sweep is what slicing was meant to prevent. A reviewer who wants it decided here can say so cheaply: it would be two more lines in core.ts.

Verification

  • Tests — three new pin files, one per row (driver-connect-code-constant.test.ts, datasource-unavailable-code-constant.test.ts, summary-recompute-code-constant.test.ts), five cases each: the literal wire string (the byte-identity fence, deliberately NOT a constant compare — a pin that reads the constant cannot catch the constant being wrong), the code a real refusal carries asserted with name and the discriminating payload (⛔ never a bare toThrow()), barrel reachability by identity, barrel-class/barrel-constant agreement, and a cross-realm copy where instanceof is false while the code compare holds. ⚠️ None of these three classes carries a status field, so ADR-0112's code + status minimum reduces to code plus the discriminating fields; ⛔ inventing a status would be new published surface and is not what this card converts.
  • pnpm --filter @objectstack/objectql exec vitest run over those three plus six existing suites that exercise these refusals — 9 files / 69 tests passed.
  • pnpm --filter @objectstack/objectql typecheck — exit 0, including check:test-typecheck: the test-layer debt ledger holds at 44 files / 242 errors / 69 pinned signatures, unchanged. The three new test files are proven inside that program by tsc --noEmit --listFiles -p tsconfig.test.json (1 hit each, firing control 1, negative control 0) and contribute 0 of the 242 — that count is the firing control on the same command.
  • Gate families: derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (7 paths vs merge base 2756e07d1), 101 runnable commands, 99 exit 0. The two that did not are NOT MEASURED rather than red, and neither is about this diff: check:dual-build-cjs-loads and check:published-readme-exports returned exit 3 PREREQUISITE NOT MET (they read every package's dist/), and check:react-declaration-parity printed "MANIFEST is not set — this gate did NOT run" (it needs an objectui build and a browser). Two further gates print NOT WIRED without a PR number and are re-run against this PR below.

Re-runs, and the head every reading above was taken at

Every gate reading in this body was taken at 0f1b41393, which is this branch's head; nothing has been pushed since.

  • The two PREREQUISITE-NOT-MET gates were re-run after a full workspace build (72 packages built) and are now real greens over a population that includes this package: check:dual-build-cjs-loads exit 0 — 103 published require entry points across 66 packages, 619 emitted CommonJS files parse (its population is read from packages/**/package.json declaring the require condition, and this package declares it on both . and ./core); check:published-readme-exports exit 0 — 60 documents across 79 workspace packages.
  • ⚠️ check:dts-closure's FIRST green was about a population that mostly excluded this tree: 15 built packages swept, because only the objectql dependency closure had been built. Re-run after the workspace build: 72 built packages, 164/164 declared declaration files present, exit 0. Quoting the first reading as evidence would have been the vacuous-green shape.
  • check:partof-closing-keyword, re-run with the posted body rather than an empty environment (it reads PR_BODY and passes vacuously when that is unset): exit 0, "PR feat(objectql): publish DriverConnect, DatasourceUnavailable and SummaryRecompute error codes as constants #16308 carries no Part-of/closing-keyword contradiction."
  • check:single-claim-paths, re-run with this PR's number: exit 0. ⚠️ Worth recording precisely, because the dispatch expected otherwise: the gate's SINGLE_CLAIM_PATHS list holds exactly one entry, .objectui-sha, and its own docblock says a shared registry or root manifest deliberately does NOT belong there. So packages/objectql/src/index.ts is not a gate-declared at-most-one-writer path, and this gate will not serialise batch ii behind this PR. Keeping the two batches serial is still right — both edit adjacent regions of one barrel and the second would likely take a textual merge conflict — but it is a merge-hygiene choice, not a mechanised one.
  • Repo-wide pnpm lint (eslint . --no-inline-config) run IN FULL, exit 0 — ⛔ no narrowing claimed. Non-vacuity control: re-running eslint over exactly the six changed/added files with --format json reports 6 files linted, 0 errors, 0 warnings, so the repo-wide green demonstrably covers them.
  • node scripts/pm/check-clause2-carriers.mjs --pair 16308 — exit 0 (captured by redirect-then-read, ⛔ never through a pipe): "the clause-② declaration is readable in the fixed spelling and both carriers agree." The checker was proved current first: its blob hash equals origin/main's.
  • needs:contract-review is hung on both carriers (this PR and objectql: 11 error classes still spell their code as an inline literal, so a consumer cannot follow the by code, not instanceof convention the docs already teach #16159) through the additive endpoint, with a comparative read-back proving union(read, target) exactly and nothing stripped — the auto-labeler's four survived. ⛔ Not released and not cleared by this round: a separate reviewing seat owns that.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ


Generated by Claude Code


Generated by Claude Code

…rror codes as constants

Both classes' own docblocks already say the refusal is "Identified by `code`
rather than `instanceof` so it survives crossing package boundaries", and
neither offered anything to import. Following that published instruction meant
re-spelling the wire string in the consumer's own package -- a
`check:error-code-provenance` stamp site there, free to drift from what this
engine throws with no compile error to say so.
`packages/rest/src/error-response.ts` does exactly that for the datasource
refusal today.

`DRIVER_CONNECT_CODE` and `DATASOURCE_UNAVAILABLE_CODE` are new exports from
`@objectstack/objectql`, re-exported from `index.ts` beside the classes they
name. Dropping the `ERR_` prefix from the constants' NAMES follows this
package's existing precedents (`HOOK_TARGET_REBIND_ERROR_CODE`,
`READONLY_FIELD_REJECTED_CODE`).

Both strings are byte-identical to the literals they replace: each quoted
spelling occurs exactly once in the file on both sides of the change -- it
moved, it did not multiply or mutate.

Both classes are ALSO published from the lean `./core` entry while the
constants, like every other `*_CODE` here, are batteries-only. That asymmetry
is #16260's subject for the whole family and is deliberately not decided by
this mechanical conversion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
`SummaryRecomputeError`'s docblock already says it is "Identified by `code`
rather than `instanceof` so it survives crossing package boundaries", and
offered nothing to import. This row's cost is the batch's most concrete: TWO
first-party packages already re-spell the wire string, and both do it to
implement the very recovery the class was designed for -- "the triggering
records WERE written, so treat a failed roll-up as a warning and keep them":
`packages/rest/src/import-runner.ts` and
`packages/metadata-protocol/src/seed-loader.ts`. Three spellings of one code
across three packages, kept equal by nothing but a grep.

`SUMMARY_RECOMPUTE_CODE` is a new export from `@objectstack/objectql`,
re-exported from `index.ts` beside the class, which is where that class is
already published. Naming and field spelling follow this package's precedents.

The string is byte-identical to the literal it replaces: the quoted spelling
occurs exactly once in the file on both sides of the change.

This commit does NOT rewire the two consumers named above -- that is a
consumer-side change in two other packages, outside a producer-side sweep, and
no gate asks for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
One changeset, three graded entries -- batching these rows into one PR changed
how many PRs the sweep costs, not how each row is graded. Each of
`DRIVER_CONNECT_CODE`, `DATASOURCE_UNAVAILABLE_CODE` and
`SUMMARY_RECOMPUTE_CODE` is additive widening of a published surface with
nothing removed, which is `minor` on its own account.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
@github-actions github-actions Bot added the size/m label Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 9 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/wire-format.mdx (via ERR_DATASOURCE_UNAVAILABLE (literal, a string literal in DATASOURCE_UNAVAILABLE_CODE; a string literal in DatasourceUnavailableError))
  • content/docs/data-modeling/external-datasources.mdx (via ERR_DATASOURCE_UNAVAILABLE (literal, a string literal in DATASOURCE_UNAVAILABLE_CODE; a string literal in DatasourceUnavailableError))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via ERR_SUMMARY_RECOMPUTE (literal, a string literal in SUMMARY_RECOMPUTE_CODE; a string literal in SummaryRecomputeError))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/index.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b9a14dd64f0e579adf94d683eda729991f6935e0packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9e341eb6dba14ebc7e8a32883b54cbfa69705659 — the merge of head 97002640edbe386774c4b780d1c7ad0a9a4ab7e8 into base b9a14dd64f0e579adf94d683eda729991f6935e0, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9e341eb6dba14ebc7e8a32883b54cbfa69705659 && git checkout 9e341eb6dba14ebc7e8a32883b54cbfa69705659
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b9a14dd64f0e579adf94d683eda729991f6935e0 97002640edbe386774c4b780d1c7ad0a9a4ab7e8 && git checkout -B drift-repro b9a14dd64f0e579adf94d683eda729991f6935e0 && git merge --no-ff 97002640edbe386774c4b780d1c7ad0a9a4ab7e8

node scripts/docs-audit/affected-docs.mjs --json b9a14dd64f0e579adf94d683eda729991f6935e0

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b9a14dd64f0e579adf94d683eda729991f6935e0 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

The consumer-side follow-up this PR's body promises but deliberately does not do is filed as #16309 — the three first-party call sites that re-spell these codes today (packages/rest/src/error-response.ts ×2, packages/rest/src/import-runner.ts, packages/metadata-protocol/src/seed-loader.ts).

⚠️ One measurement in it is worth knowing before anyone treats it as a single mechanical sweep: packages/rest already declares "@objectstack/objectql": "workspace:*" and can import the constants today, but packages/metadata-protocol does not depend on @objectstack/objectql at all. Converting its site would add a dependency from the protocol layer onto the query engine, which may be the wrong direction on purpose — a layering question, not an import.


Generated by Claude Code

Each of the three new `*_CODE` constants sat BETWEEN its class's docblock and
the class. Two consecutive JSDoc blocks both attach to the declaration that
follows, so the emitted declarations carried both blocks on the CONSTANT and
`declare class ...Error` shipped undocumented — measured on the package's own
`tsup` emitter, not inferred.

Move each constant and its own docblock ABOVE the class docblock, the grouped
shape `registry.ts` already uses. Pure line reordering: the sorted line multiset
of both files is byte-identical to the previous commit's, and the exported name
set of every declaration file `files[]` publishes is unchanged.

Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up commit 97002640e — a reordering of two lines per site, and nothing else.

Defect. Each new *_CODE constant sat BETWEEN its class's own docblock and the class. Two consecutive JSDoc blocks both attach to the declaration that follows, so in the emitted declarations both blocks landed on the CONSTANT and each class shipped undocumented. Found by the batch-ii round's counterfactual on the real tsup emitter; reproduced here on this branch's own head before anything was changed (npx tsup inside packages/objectql, no turbo on the path, so no cache).

Before — emitted dist/index.d.ts:

341 /**
342  * Thrown by engine.insert/update/delete when one or more parent roll-up
 ...   (the CLASS docblock)
351  */
352 /**
353  * [#16159] The ADR-0112 `code` {@link SummaryRecomputeError} carries, as a
 ...   (the CONSTANT docblock)
388  */
389 declare const SUMMARY_RECOMPUTE_CODE: "ERR_SUMMARY_RECOMPUTE";
390 declare class SummaryRecomputeError extends Error {

After — same file, rebuilt:

377  */
378 declare const SUMMARY_RECOMPUTE_CODE: "ERR_SUMMARY_RECOMPUTE";
379 /**
380  * Thrown by engine.insert/update/delete when one or more parent roll-up
 ...
389  */
390 declare class SummaryRecomputeError extends Error {

DriverConnectError and DatasourceUnavailableError read the same way in the shared chunk dist/util-*.d.ts — before, declare const then declare class with zero lines between them (2321/2322 and 2396/2397); after, the class docblock sits between them (2300-2321 and 2372-2396).

Fix. Each constant and its own docblock moved ABOVE the class docblock — the grouped shape registry.ts already landed, rather than a third arrangement.

Proof it is only a reordering.

  • The sorted line multiset of both source files is byte-identical to the previous commit's: a pure permutation, 58 insertions / 58 deletions.
  • Exported NAME SET across every declaration file files[] publishes — index.d.ts 255 to 255, index.d.mts 255 to 255, core.d.ts and core.d.mts 79 to 79, shared chunk 135 to 135; ADDED = [] and REMOVED = [] on all six. Instrument controlled both ways: it sees the three new constants, and a synthetic one-name delta fires it.
  • The index.d.ts byte diff classifies as exactly two hunks — the content-hashed chunk filename, and the class docblock moving. core.d.ts differs only by that filename, which is build noise and not a surface change.
  • Rebuild proven rather than assumed: every declaration file's mtime moved 14:47:37Z to 14:50:21Z and its hash changed.

Checks. pnpm --filter @objectstack/objectql typecheck exit 0 (the test-typecheck ratchet holds at 44 files / 242 errors / 69 pinned signatures). Every objectql test naming these three classes or constants: 7 files, 53 tests, all passed. Repo-wide pnpm lint exit 0. check:doc-authoring, check:error-code-casing, check:dispatcher-error-vocabulary, check:error-code-provenance, check:nul-bytes and check:dts-closure (15 built packages swept, objectql among them) all exit 0.

Still a draft; needs:contract-review untouched.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant